TL;DR
In modern web development, Next.js has become more than just a framework. it has established itself as a standard. It has been chosen by countless developers worldwide for its exceptional performance and superior developer experience. At the heart of this success are powerful features like rendering optimization techniques—including Server-Side Rendering (SSR) and Static Site Generation (SSG)—and Middleware, which allows for precise control over the request-response cycle. These tools have enabled developers to efficiently build fast, scalable, and SEO-friendly applications.
However, technological advancement is always a double-edged sword. While the framework’s strengths maximize productivity, a lack of understanding of its internal workings can provide an opening for unexpected security threats. The purpose of this report is not merely to list known vulnerabilities in Next.js but to provide a deep analysis of specific 1-day vulnerabilities. By doing so, we aim to identify their root causes and propose practical directions for secure development. This report investigates CVEs in Next.js such as Cache Poisoning, SSRF, and Middleware Bypass.
What is Next.js?
Next.js is a React framework for building full-stack web applications. You use React Components to build user interfaces, and Next.js for additional features and optimizations.
According to the official documentation, Next.js is “the React Framework for the Web,” used to build UIs with the React library while implementing additional features and optimizations like Server-Side Rendering (SSR), routing, and API creation.
Key Features of Next.js
1. Rendering Strategies
One of the first key features of Next.js is its rendering strategies. Next.js employs a hybrid approach, allowing you to combine various rendering methods on a per-page basis within a single application. This enables you to achieve optimal performance and user experience by using the most suitable rendering strategy for the characteristics of each page.
SSR (Server Side Rendering)
SSR is a method where the server dynamically generates the page each time a user requests it. It’s suitable for pages with frequently changing data and is advantageous for Search Engine Optimization (SEO) as it always reflects the latest content. However, since rendering occurs on the server for every request, it can increase server load.
1
2
3export async function getServerSideProps() {
return { props: { data: await fetchData() } };
}SSG (Static Site Generation)
Unlike SSR, SSG pre-generates HTML at build time. Because it serves static pages instantly via a CDN, it is fast. The downside is that the entire application must be rebuilt if the data changes.
1
2
3export async function getStaticProps() {
return { props: { data: await fetchData() } };
}ISR (Incremental Static Regeneration)
This method complements the drawbacks of SSG. After an initial build, pages are regenerated and updated in the background at set intervals.1
2
3
4
5
6export async function getStaticProps() {
return {
props: { data: await fetchData() },
revalidate: 60, // regenerate page every 60sec
};
}CSR (Client-Side Rendering)
It is a method of requesting data from a client tofetch
oraxios
, receiving it, and completing HTML.1
2
3
4"use client";
useEffect(() => {
fetch("/api/data").then(setData);
}, []);RSC (React Server Component)
Although RSC (React Server Component) is a technology for rendering components on the server, the process of fetching the result to the client is closer to a data communication method. The client requests an RSC payload, which is like a fragment, rather than the entire HTML. This request is included with theAccept
header when the client makes afetch
request, and the server returns the RSC data in a binary format.1
2
3
4
5
6
7
8GET /dashboard?_rsc=abc123 HTTP/1.1
Accept: text/x-component
RSC: 1
HTTP/1.1 200 OK
Content-Type: text/x-component
<RSC Payload>
2. Routing
The Next.js routing system is based on the file system. This allows you to intuitively set up page paths simply by creating folders and files, eliminating the need for complex routing configurations.
Static Routing
Creates URL paths with fixed names. When you create a folder and a
page.js
file inside it, the folder name becomes the URL path.1
2
3
4
5
6app/
├── about/
│ └── page.js ---> http://www.host.com/about
└── products/
└── all/
└── page.js ---> http://www.host.com/products/allDynamic Routing
Handles pages where part of the URL changes. This is done by using square brackets in the folder name, like[folderName]
.1
2
3
4app/
└── blog/
└── [slug]/
└── page.js ---> /blog/post1, /blog/post2 ...
Why Next.js?
Next.js is particularly useful for the following types of projects:
- Websites where Search Engine Optimization(SEO) is critical.
- Platforms that require high performance.
- Complex dashboards and web applications.
CVE Analyze
1. CVE-2023-46298 (DoS via cache poisoning)
CVE-2023-48298
is a Denial of Service (DoS) vulnerability through cache poisoning that occurs on Server-Side Rendering (SSR) pages. It allows for a specially crafted prefetch request to induce the server to return an empty response. At this point, since the response lacks a Cache-Control
header, a CDN may cache this empty response. Consequently, all subsequent normal users accessing the same page will not receive the intended data from the CDN, making them unable to use the service.
Part1. SSR, The Standard of Frontend
SSR is an indispensable technology in modern frontend development. Since it generates and returns HTML from the server, it’s a suitable technology for pages that return dynamic data.
However, there is a latency since it has to render a new HTML for every request. To solve this, a technology called prefetch
exists. Prefetch
is a technology that “pre-downloads the data for a page that the client is likely to request.”
1 | // index.tsx |
When a user first visits this index page, the Link component renders, and Next.js pre-requests the JSON data needed for the /ssr page (only the data, not the full HTML).
prefetch request
prefetch request details
When you access localhost:3000 and index.tsx is rendered, a prefetch
request occurs to /_next/data/<BUILD_ID>/ssr.json
, and you can see that you are receiving pageProps data for use in /ssr.
Part2. Cache and CDN
CDNs like CloudFront will use a default cache duration (e.g., 24 hours) for caching if the cache-control
header is absent in the response packet. If an “empty object” is what gets cached by the CDN, the Cache Poisoning vulnerability occurs where all users of the same URL receive an empty value. This is the cause of CVE-2023-46298
.
Cache Poisoning diagram
Then let’s see how we can get them to return empty objects in the absence of a cache-control header
.
Part3. Root Cause
1 | // https://github.com/vercel/next.js/blob/v13.4.19/packages/next/src/server/base-server.ts#L1487 |
The renderToResponseWithComponentsImpl
function is the implementation that handles SSR requests. If the x-middleware-prefetch
header’s value is 1 (true), an empty object is returned without a cache-control header. If a middleware exists or an attacker maliciously adds this header to the request, the empty object will be cached on the CDN, causing the Denial of Service (DoS) attack to succeed for all users accessing the same URL.
Part4. Proof of Concept
Normal Prefetch Request
In a normal prefetch request, the response is returned correctly along with a cache-control
header. The CDN will check this header to determine whether and how long to cache the response.
Bad Prefetch Packet
However, if the x-middleware-prefetch: 1
header is present in the same request, an empty value is returned without a cache-control
header. This causes a bug where the CDN, following its default caching policy (e.g., 24 hours for CloudFront), will return an empty value for subsequent requests to that URL.
NextJS-PoC/PoC-cve-2023-46298 at main · aest3ra/NextJS-PoC
Part5. Remediating and Defending
Diffing
In Next.js version 13.4.20-canary.13, this was fixed by adding a cache-control
header to the response, preventing Cache Poisoning.
2. CVE-2025-29927 (Middleware Bypass)
CVE-2025-29927
is a vulnerability where an attacker can bypass the entire middleware logic by manipulating the x-middleware-subrequest
header, a special header internally designed to prevent infinite loops in middleware.
Part1. Middleware of Next.js
Next.js Middleware is a feature that allows code to be executed before the request is finally processed. Other frameworks allow middleware to be implemented using libraries, but Next.js handles middleware requests in src/middleware.js
1 | // src/middleware.ts |
Middleware is commonly used for authentication and authorization. Therefore, being able to bypass the middleware itself would allow for bypass most verification logic.
Part2. Root Cause
1 | // https://github.com/vercel/next.js/blob/v15.2.2/packages/next/src/server/web/sandbox/sandbox.ts#L105 |
This part causes CVE-2025-29927
. To start with, you can ignore middleware if you enter the conditional statement. For this, the following conditions must be met.
- Request with
x-middleware-subrequest
header - Array length parsed based on header value
:
should be at least 5 - The value of each array must be equal to the value of
params.name
If the
x-middleware-subrequest
header exists, its value is split by:
and stored insubrequests
1
2x-middleware-subrequest: aaa:bbb:ccc
subrequests -> ['aaa', 'bbb', 'ccc']Check how many values
params.name
match each value in thesubrequests
array, and store the number of matches in the depth variable.params.name
is the path of middleware. After Next.js version 13, it becomes eithermiddleware
orsrc/middleware
.Therefore, the final payload is based on Next.js version 13 and later, as we need to have at least 5
params.name
values in the array:1
2
3
4
5
6GET /admin HTTP/1.1
x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware
// if "middleware.js" or "middleware.ts" is in src/ folder
GET /admin HTTP/1.1
x-middleware-subrequest: src/middleware:src/middleware:src/middleware:src/middleware:src/middleware
Part3. Proof of Concept
The /admin
path is protected by the middleware’s authorization logic, so if you send a request without permission, it is redirected to the /
path.
By adding the x-middleware-subrequest
header and its value appropriately to the same path, it can be seen that the /admin
path has been successfully approached.
NextJS-PoC/PoC-cve-2025-29927 at main · aest3ra/NextJS-PoC
Part4. Remediating and Defending
The server side generates a globally used x-middleware-subrequest-id
value and receives x-middleware-subrequest-id
and x-middleware-subrequest
values when verifying middleware-related headers, which are patched to verify that the x-middleware-subrequest
header is generated by the server.
Update middleware request header (#77201) · vercel/next.js@52a078d
3. CVE-2024-46982 (Cache poisoning)
CVE-2024-46982
is a vulnerability that can pollute the cache of a static server-side rendering endpoint by adding headers internally used by the Next.js server within an HTTP request. By default, endpoints where server-side rendering occurs should not be cached regardless of whether they are dynamic or static. However, if a specific HTTP header and parameter are added to a request to a non-dynamic server-side rendering endpoint on a server affected by this CVE, it becomes possible to include Cache-Control: s-maxage=1, stale-while-revalidate
in the server’s response. In other words, it means that response caching can be forced. Then, let’s find out which header needs to be added to induce cache poisoning, and how the added header was parsed within the Next.js server, resulting in the vulnerability.
Affected
- Next.js
>= 13.5.1
,< 14.2.10
- In the case of using an SSR route that is not dynamic
1
pages/dashboard.tsx (vulnerable)
1
pages/blog/[slug].tsx (not vulnerable)
- In the case of using an SSR route that is not dynamic
Part1. x-now-route-matches
The following header must be added to the HTTP request to induce cache poisoning.
1 | x-now-route-matches: 1 |
First is the x-now-route-matches
header. This header is internally used by Next.js to determine whether the current request is an SSG
request. An SSG
(Static Site Generation) request refers to a feature where HTML page data is pre-generated at build time for faster response and caching.
1 | ... |
This is the code that shows how Next.js handles a request when the x-now-route-matches
header is present. If the x-now-route-matches
header exists in the request, the isSSG
variable is set to true
, causing the request to be recognized as an SSG
request. When Next.js recognizes a request as an SSG
request, it sets the following cache headers and behaves in a way that caches the response.
1 | Cache-Control: s-maxage=N, stale-while-revalidate |
Part2. Root Cause
The root cause of the CVE can be easily identified by checking base-server.ts
.
1 | if ( |
First, it retrieves and references the x-now-route-matches
header from the request, but it does not verify whether the header value was passed from an external source or generated internally.
1 | ... |
Second, the revalidate
value seen in the code is an option used within getStaticProps()
that defines the caching period to ensure minimal cache validity. By inspecting the Next.js code, we can see that if revalidate
is not set, a ternary operator assigns it a value of 1
. When revalidate
is set to 1
, the page is cached for about one second, making it possible to cache pages that were not originally configured for caching. These two factors together lead to the vulnerability.
Part3. PoC for CVE-2024-46982
The PoC for CVE-2024-46982
is very simple. By adding the x-now-route-matches
header to the HTTP request as shown below, the vulnerability can be demonstrated.
However, in the Postman request shown in the attached image, we can observe the presence of the X-Nextjs-Cache
header and the __nextDataReq
parameter in addition to the x-now-route-matches
header. These are closer to elements used for exploiting the vulnerability at a more advanced level rather than being part of the basic PoC.
First, the __nextDataReq
parameter is used to request elements such as pageProps
, __N_SSP
, etc., which are used for page composition, rather than the actual HTML content that should be returned from the path. Therefore, when an HTTP request is sent with this parameter, only the components used for building the page are returned instead of the HTML page, which can lead to a DoS condition (as a regular user does not receive the expected page content).
Additionally, X-Nextjs-Cache
is a header that, simply put, indicates what kind of caching behavior occurred in response to the request. In the PoC, the value is set to INVALIDATE
, which means that previously stored cache is invalidated.
Thus, the PoC can be interpreted as follows:
x-now-route-matches:1
: This request is an SSG request.__nextDataReq
: Return only the page composition elements.X-Nextjs-Cache:INVALIDATE
: Invalidate the cache.
As a result, the existing cache is invalidated, and the server caches the page composition elements and returns them to all users.
At this point, if client input values such as User-Agent
are included in the server response and an XSS or similar vulnerability occurs, it can lead to a supply-chain-style XSS attack targeting all users.
Part4. Remediating and Defending
https://github.com/vercel/next.js/commit/7ed7f125e07ef0517a331009ed7e32691ba403d3
The patch was applied as follows. The existing behavior, where the revalidate
value was automatically set to 1
if not specified by the developer, was changed so that it remains in an undefined
state. During rendering, if the revalidate
value is not explicitly set, the logic was modified to explicitly assign it a value of 0
.
Upon seeing this patch, one might question, “Shouldn’t the root cause—the fact that the x-now-route-matches
header affects internal server behavior—be directly blocked?” This is certainly a valid question. However, instead of preventing externally provided x-now-route-matches
headers from influencing internal behavior, the Next.js team chose to patch the logic so that caching does not occur unless the developer explicitly sets a revalidate
value for the route.
This decision was likely made to maintain simplicity in the patch and to minimize the potential impact on other functionalities.
4. CVE-2024-34351 (SSRF)
CVE-2024-34351
is an SSRF vulnerability that allows an attacker to send an HTTP GET request to an arbitrary IP address by manipulating the Host
header and adding headers used only by Next.js. In typical SSRF vulnerabilities, the issue usually arises when the server contains code that sends a request to a URL controllable by the client. However, in this case, the vulnerability can occur even without such logic on the server side. Moreover, it is even possible to observe the response to the HTTP request. Below is an analysis of this vulnerability.
Affected
- NextJS
>= 13.4.0
,< 14.1.1
- NextJS running with SELF-HOSTING
- NextJS application uses Server-Action
- Server-Action perform relative-Path Redirection
Part1. Next.js Server-Action
Before understanding this CVE, it is important to first understand the concept of Server Actions used in Next.js. Server Actions are a method of invoking server-side functions within the App Router, based on the RSC (React Server Components) architecture. To explain this more simply, let’s continue with an example code snippet:
1 | // app/page.tsx |
Let’s assume the following code exists. The createPost()
function is called on the server side. When the logic is structured as shown above, the {createPost}
part in <form action={createPost}>
is replaced with an action ID that identifies the server-side createPost()
function. When the form is submitted, the server receives the action ID and invokes the corresponding server function mapped to that ID.
Part2. Root Cause
The vulnerability occurred during the process of handling the Server Action described above. This can be confirmed through the following code logic.
The code below is responsible for handling Server Action calls.
1 | ... |
Looking at the code logic, we can see that the Host
header is referenced with const host = originalHost.value
to generate the fetchUrl
. A HEAD request is then sent to that URL, and if the response content-type
is RSC_CONTENT_TYPE_HEADER
(text/x-component
), a GET request is sent.
1 | export const RSC_CONTENT_TYPE_HEADER = "text/x-component" as const; |
It can also be seen that after the GET request is sent, the response body is referenced to generate the rendering result. This logic is what led to the vulnerability.
Part3. PoC for CVE-2024-34351
As can be seen from the root cause, the following conditions must be met and known in order to demonstrate the vulnerability:
- Whether
Server Actions
are used and the Action ID (a value visible on the client side) - A custom server that returns
text/x-component
as thecontent-type
in response to aHEAD
request and redirects the requester to an arbitrary URL upon aGET
request
Therefore, to reproduce the vulnerability, a server like the one below must be running:
1 | from flask import Flask, Response, request, redirect |
You can reproduce the vulnerability by locating the Action ID exposed on the client side and sending a request to the server as shown below.
1 | POST / HTTP/1.1 |
This will allow you to demonstrate the vulnerability.
Part4. Remediating and Defending
https://github.com/vercel/next.js/commit/8f7a6ca7d21a97bc9f7a1bbe10427b5ad74b9085
The patch was applied as follows.
The action handling logic and the server startup logic were modified. First, in the action handling logic where the vulnerability occurred, it was changed to prioritize referencing process.env.__NEXT_PRIVATE_HOST
over originalHost.value
. Additionally, during server startup, a new logic was added to set process.env.__NEXT_PRIVATE_HOST
, so that under normal circumstances, requests are not sent based on headers received from the client.
From the nature of these changes, it appears that if the environment variable is modified or process.env
can be manipulated through an attack such as prototype pollution, SSRF may still be possible.
Conclusion
Through researching Next.js, I was able to identify many unique features that differentiate it from other web frameworks. These characteristics clearly offer advantages in terms of user convenience and server performance. However, as seen in most of the CVEs introduced above, the lack of proper header management leads to various vulnerabilities. Through these CVEs, I hope that as Next.js continues to evolve and introduce new features, it will also adopt a more security-conscious approach by not trusting external input values without validation, ultimately resulting in a more robust and secure framework.
References
https://zhero-web-sec.github.io/research-and-things/nextjs-cache-and-chains-the-stale-elixir
https://zhero-web-sec.github.io/research-and-things/nextjs-and-the-corrupt-middleware